package com.zbangmall.util.web;


import com.zbangmall.util.common.CharUtil;
import com.zbangmall.util.common.CharsetUtil;
import com.zbangmall.util.common.StringUtil;

import javax.servlet.http.HttpServletRequest;
import java.io.ByteArrayOutputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * URL相关的工具类
 * <p>
 * 来自开源项目jodd
 * {@literal https://jodd:ddoj@www.jodd.org:8080/file;p=1?q=2#third}.
 */
public final class UrlUtil {
	private static final String SCHEME_PATTERN = "([^:/?#]+):";

	private static final String HTTP_PATTERN = "(http|https):";

	private static final String USERINFO_PATTERN = "([^@/]*)";

	private static final String HOST_PATTERN = "([^/?#:]*)";

	private static final String PORT_PATTERN = "(\\d*)";

	private static final String PATH_PATTERN = "([^?#]*)";

	private static final String QUERY_PATTERN = "([^#]*)";

	private static final String LAST_PATTERN = "(.*)";

	/**
	 * 匹配URI的正则表达式模式
	 */
	private static final Pattern URI_PATTERN = Pattern.compile("^(" + SCHEME_PATTERN + ")?" + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN +
			")?" + ")?" + PATH_PATTERN + "(\\?" + QUERY_PATTERN + ")?" + "(#" + LAST_PATTERN + ")?");

	private static final Pattern HTTP_URL_PATTERN = Pattern.compile('^' + HTTP_PATTERN + "(//(" + USERINFO_PATTERN + "@)?" + HOST_PATTERN + "(:" + PORT_PATTERN + ")?" + ")?" +
			PATH_PATTERN + "(\\?" + LAST_PATTERN + ")?");

	/**
	 * 编码单个URI组件
	 */
	private static String encodeUriComponent(String source, String encoding, URIPart uriPart) {
		if (source == null) {
			return null;
		}

		byte[] bytes;
		try {
			bytes = encodeBytes(source.getBytes(encoding), uriPart);
		} catch (UnsupportedEncodingException ignore) {
			return null;
		}

		char[] chars = new char[bytes.length];
		for (int i = 0; i < bytes.length; i++) {
			chars[i] = (char) bytes[i];
		}
		return new String(chars);
	}

	/**
	 * 使用允许字符编码字节数组 {@link URIPart}.
	 */
	private static byte[] encodeBytes(byte[] source, URIPart uriPart) {
		ByteArrayOutputStream bos = new ByteArrayOutputStream(source.length);
		for (byte b : source) {
			if (b < 0) {
				b += 256;
			}
			if (uriPart.isValid((char) b)) {
				bos.write(b);
			} else {
				bos.write('%');
				char hex1 = Character.toUpperCase(Character.forDigit((b >> 4) & 0xF, 16));
				char hex2 = Character.toUpperCase(Character.forDigit(b & 0xF, 16));
				bos.write(hex1);
				bos.write(hex2);
			}
		}
		return bos.toByteArray();
	}

	/**
	 * 用给定的编码对给定的URI方案进行编码.
	 */
	public static String encodeScheme(String scheme, String encoding) {
		return encodeUriComponent(scheme, encoding, URIPart.SCHEME);
	}

	public static String encodeScheme(String scheme) {
		return encodeUriComponent(scheme, CharsetUtil.UTF_8, URIPart.SCHEME);
	}

	/**
	 * 用给定的编码对给定的URI用户信息进行编码.
	 */
	public static String encodeUserInfo(String userInfo, String encoding) {
		return encodeUriComponent(userInfo, encoding, URIPart.USER_INFO);
	}

/*	/**
	 * 用给定的编码对给定的URI权限进行编码.
	 *

	public static String encodeAuthority(String authority, String encoding) {
		return encodeUriComponent(authority, encoding, URIPart.AUTHORITY);
	}
	public static String encodeAuthority(String authority) {
		return encodeUriComponent(authority, JoddCore.encoding, URIPart.AUTHORITY);
	}
*/

	public static String encodeUserInfo(String userInfo) {
		return encodeUriComponent(userInfo, CharsetUtil.UTF_8, URIPart.USER_INFO);
	}

	/**
	 * 用给定的编码对给定的URI主机进行编码.
	 */
	public static String encodeHost(String host, String encoding) {
		return encodeUriComponent(host, encoding, URIPart.HOST);
	}

	public static String encodeHost(String host) {
		return encodeUriComponent(host, CharsetUtil.UTF_8, URIPart.HOST);
	}

	/**
	 * 用给定的编码对给定的URI端口进行编码.
	 */
	public static String encodePort(String port, String encoding) {
		return encodeUriComponent(port, encoding, URIPart.PORT);
	}

	public static String encodePort(String port) {
		return encodeUriComponent(port, CharsetUtil.UTF_8, URIPart.PORT);
	}

	/**
	 * 用给定的编码对给定的URI路径进行编码.
	 */
	public static String encodePath(String path, String encoding) {
		return encodeUriComponent(path, encoding, URIPart.PATH);
	}

	public static String encodePath(String path) {
		return encodeUriComponent(path, CharsetUtil.UTF_8, URIPart.PATH);
	}

	/**
	 * 用给定的编码对给定的URI路径段进行编码.
	 */
	public static String encodePathSegment(String segment, String encoding) {
		return encodeUriComponent(segment, encoding, URIPart.PATH_SEGMENT);
	}

	public static String encodePathSegment(String segment) {
		return encodeUriComponent(segment, CharsetUtil.UTF_8, URIPart.PATH_SEGMENT);
	}

	/**
	 * 用给定的编码对给定的URI查询进行编码.
	 */
	public static String encodeQuery(String query, String encoding) {
		return encodeUriComponent(query, encoding, URIPart.QUERY);
	}

	public static String encodeQuery(String query) {
		return encodeUriComponent(query, CharsetUtil.UTF_8, URIPart.QUERY);
	}

	/**
	 * 用给定的编码对给定的URI查询参数进行编码.
	 */
	public static String encodeQueryParam(String queryParam, String encoding) {
		return encodeUriComponent(queryParam, encoding, URIPart.QUERY_PARAM);
	}

	public static String encodeQueryParam(String queryParam) {
		return encodeUriComponent(queryParam, CharsetUtil.UTF_8, URIPart.QUERY_PARAM);
	}

	/**
	 * 用给定的编码对给定的URI片段进行编码.
	 */
	public static String encodeFragment(String fragment, String encoding) {
		return encodeUriComponent(fragment, encoding, URIPart.FRAGMENT);
	}

	public static String encodeFragment(String fragment) {
		return encodeUriComponent(fragment, CharsetUtil.UTF_8, URIPart.FRAGMENT);
	}

	/**
	 * @see #encode(String, String)
	 */
	public static String encode(String uri) {
		return encode(uri, CharsetUtil.UTF_8);
	}

	/**
	 * 将给定源URI编码成编码字符串。所有的URI组件都是根据它们各自的有效字符集编码。
	 * 此方法不尝试对 "=" 和 "&" 进行编码
	 * 查询参数名称和查询参数值中的字符，因为它们不能以可靠的方式进行分析。
	 */
	public static String encode(String uri, String encoding) {
		Matcher m = URI_PATTERN.matcher(uri);
		if (m.matches()) {
			String scheme    = m.group(2);
			String authority = m.group(3);
			String userinfo  = m.group(5);
			String host      = m.group(6);
			String port      = m.group(8);
			String path      = m.group(9);
			String query     = m.group(11);
			String fragment  = m.group(13);

			return encodeUriComponents(scheme, authority, userinfo, host, port, path, query, fragment, encoding);
		}
		throw new IllegalArgumentException("Invalid URI: " + uri);
	}

	/**
	 * @see #encodeHttpUrl(String, String)
	 */
	public static String encodeHttpUrl(String httpUrl) {
		return encodeHttpUrl(httpUrl, CharsetUtil.UTF_8);
	}

	/**
	 * 将给定的HTTP URI编码为编码字符串。所有的URI组件都是根据它们各自的有效字符集编码。
	 * 查询参数名称和查询参数值中的字符，因为它们不能以可靠的方式进行分析
	 */
	public static String encodeHttpUrl(String httpUrl, String encoding) {
		Matcher m = HTTP_URL_PATTERN.matcher(httpUrl);
		if (m.matches()) {
			String scheme     = m.group(1);
			String authority  = m.group(2);
			String userinfo   = m.group(4);
			String host       = m.group(5);
			String portString = m.group(7);
			String path       = m.group(8);
			String query      = m.group(10);

			return encodeUriComponents(scheme, authority, userinfo, host, portString, path, query, null, encoding);
		}
		throw new IllegalArgumentException("Invalid HTTP URL: " + httpUrl);
	}

	private static String encodeUriComponents(String scheme, String authority, String userInfo, String host,
											  String port, String path, String query, String fragment, String encoding) {

		StringBuilder sb = new StringBuilder();

		if (scheme != null) {
			sb.append(encodeScheme(scheme, encoding));
			sb.append(':');
		}

		if (authority != null) {
			sb.append("//");
			if (userInfo != null) {
				sb.append(encodeUserInfo(userInfo, encoding));
				sb.append('@');
			}
			if (host != null) {
				sb.append(encodeHost(host, encoding));
			}
			if (port != null) {
				sb.append(':');
				sb.append(encodePort(port, encoding));
			}
		}

		sb.append(encodePath(path, encoding));

		if (query != null) {
			sb.append('?');
			sb.append(encodeQuery(query, encoding));
		}

		if (fragment != null) {
			sb.append('#');
			sb.append(encodeFragment(fragment, encoding));
		}

		return sb.toString();
	}

	/**
	 * 创建URL生成器，用于构建URL的用户友好方式
	 * 提供路径解析{@link #encode(String) encoded}.
	 *
	 * @see #build(String, boolean)
	 */
	public static Builder build(String path) {
		return build(path, true);
	}

	/**
	 * 创建具有可选路径的URL生成器。因为大多数时间路径是有效的，不需要被编码，使用这种方法来获得一些性能。
	 * 当编码标志关闭时，所提供的路径无需处理。
	 * Builder的目的是帮助查询参数。所有其他URI部件应该预先设置或在URL被建立之后
	 */
	public static Builder build(String path, boolean encodePath) {
		return new Builder(path, encodePath, CharsetUtil.UTF_8);
	}

	/**
	 * 解码URL元素.
	 */
	public static String decode(String url) {
		return decode(url, CharsetUtil.UTF_8, false);
	}

	/**
	 * 解码URL元素。此方法可用于所有URL的部分，除了查询部分之外，因为它是不解码"+"字符。
	 *
	 * @see #decodeQuery(String, String)
	 */
	public static String decode(String source, String encoding) {
		return decode(source, encoding, false);
	}

	/**
	 * 解码查询名称或值.
	 */
	public static String decodeQuery(String source) {
		return decode(source, CharsetUtil.UTF_8, true);
	}

	/**
	 * 解码查询名称或值.
	 */
	public static String decodeQuery(String source, String encoding) {
		return decode(source, encoding, true);
	}

	private static String decode(String source, String encoding, boolean decodePlus) {
		int                   length = source.length();
		ByteArrayOutputStream bos    = new ByteArrayOutputStream(length);

		boolean changed = false;

		for (int i = 0; i < length; i++) {
			int ch = source.charAt(i);
			switch (ch) {
				case '%':
					if ((i + 2) < length) {
						char hex1 = source.charAt(i + 1);
						char hex2 = source.charAt(i + 2);
						int  u    = Character.digit(hex1, 16);
						int  l    = Character.digit(hex2, 16);
						if (u == -1 || l == -1) {
							throw new IllegalArgumentException("Invalid sequence: " + source.substring(i));
						}
						bos.write((char) ((u << 4) + l));
						i += 2;
						changed = true;
					} else {
						throw new IllegalArgumentException("Invalid sequence: " + source.substring(i));
					}
					break;

				case '+':
					if (decodePlus) {
						ch = ' ';
						changed = true;
					}

				default:
					bos.write(ch);
			}
		}
		try {
			return changed ? new String(bos.toByteArray(), encoding) : source;
		} catch (UnsupportedEncodingException ignore) {
			return null;
		}
	}

	public final static String setParam(String url, String paramName, String paramValue) {
		int temp_index = url.indexOf("?");
		if (temp_index != -1) {
			int param_index = url.indexOf(paramName + "=", temp_index + 1);
			if (param_index != -1) {
				temp_index = url.indexOf("&", param_index + paramName.length() + 1);
				if (temp_index != -1) {
					return url.substring(0, param_index) + paramName + "=" + paramValue + url.substring(temp_index);
				}
				return url.substring(0, param_index) + paramName + "=" + paramValue;
			} else {
				if (url.lastIndexOf("&") == url.length() - 1) {
					return url + paramName + "=" + paramValue;
				}
				return url + "&" + paramName + "=" + paramValue;
			}
		} else {
			return url + "?" + paramName + "=" + paramValue;
		}
	}

	public static final String getParamValue(String url, String paramName) {
		int temp_index = url.indexOf("?");
		if (temp_index != -1) {
			int param_index = url.indexOf(paramName + "=", temp_index + 1);
			if (param_index != -1) {
				temp_index = url.indexOf("&", param_index + paramName.length() + 1);
				if (temp_index != -1) {
					return url.substring(param_index + paramName.length() + 1, temp_index);
				}
				return url.substring(param_index + paramName.length() + 1);
			} else {
				return null;
			}
		} else {
			return null;
		}
	}

	public static final String removeParam(String url, String... paramNames) {
		for (String paramName : paramNames) {
			url = removeParam(url, paramName);
		}
		return url;
	}

	public static final String removeParam(String url, String paramName) {
		int temp_index = url.indexOf("?");
		if (temp_index != -1) {
			int param_index = url.indexOf(paramName + "=", temp_index + 1);
			if (param_index != -1) {
				temp_index = url.indexOf("&", param_index + paramName.length() + 1);
				if (temp_index != -1) {
					return url.substring(0, param_index) + url.substring(temp_index + 1);
				}
				return url.substring(0, param_index - 1);

			} else {
				return url;
			}
		} else {
			return url;
		}
	}

	/**
	 * 例子:
	 * <pre>
	 * {@code
	 * url: http://tt.se/                 Location: /start              =>  http://tt.se/start
	 * url: http://localhost/moved_perm   Location: /                   =>  http://localhost/
	 * url: http://github.com/            Location: http://github.com/  =>  https://github.com/
	 * }
	 *
	 * (如果新URL抛出了MalFrimurURLExchange，URL字符串表示将返回.)
	 */
	public static String urlJoin(URL url, String locationHeader) {
		try {
			if (locationHeader.startsWith("http")) {
				return new URL(locationHeader).toString();
			}
			return new URL(url.getProtocol() + "://" + url.getAuthority() + locationHeader).toString();
		} catch (MalformedURLException e) {
			return url.toString();
		}

	}

	/**
	 * 获取HTTPrequest的请求参数
	 *
	 * @param request http请求
	 */
	public static Map print(HttpServletRequest request) {
		Map<String, String> map        = new HashMap<>();
		Enumeration         paramNames = request.getParameterNames();
		while (paramNames.hasMoreElements()) {
			String   paramName   = (String) paramNames.nextElement();
			String[] paramValues = request.getParameterValues(paramName);
			if (paramValues.length == 1) {
				String paramValue = paramValues[0];
				if (paramValue.length() != 0) {
					map.put(paramName, paramValue);
				}
			}
		}
		return map;
	}

	/**
	 * 解析字符串返回map键值对(例：a=1&b=2 => a=1,b=2)
	 *
	 * @param query   源参数字符串
	 * @param split1  键值对之间的分隔符（例：&）
	 * @param split2  key与value之间的分隔符（例：=）
	 * @param dupLink 重复参数名的参数值之间的连接符，连接后的字符串作为该参数的参数值，可为null
	 *                null：不允许重复参数名出现，则靠后的参数值会覆盖掉靠前的参数值。
	 * @return map
	 */
	public static Map<String, String> parseQuery(String query, char split1, char split2, String dupLink) {
		if (!StringUtil.isEmpty(query) && query.indexOf(split2) > 0) {
			Map<String, String> result = new HashMap<>();

			String name      = null;
			String value     = null;
			String tempValue = "";
			int    len       = query.length();
			for (int i = 0; i < len; i++) {
				char c = query.charAt(i);
				if (c == split2) {
					value = "";
				} else if (c == split1) {
					if (!StringUtil.isEmpty(name) && value != null) {
						if (dupLink != null) {
							tempValue = result.get(name);
							if (tempValue != null) {
								value += dupLink + tempValue;
							}
						}
						result.put(name, value);
					}
					name = null;
					value = null;
				} else if (value != null) {
					value += c;
				} else {
					name = (name != null) ? (name + c) : "" + c;
				}
			}

			if (!StringUtil.isEmpty(name) && value != null) {
				if (dupLink != null) {
					tempValue = result.get(name);
					if (tempValue != null) {
						value += dupLink + tempValue;
					}
				}
				result.put(name, value);
			}

			return result;
		}
		return null;
	}

	/**
	 * 解析http请求URI
	 *
	 * @param queryUri http请求的uri
	 */
	public static Map<String, String> httpParseQuery(String queryUri) {
		Map<String, String> result = new HashMap<>();
		if (!StringUtil.isEmpty(queryUri)) {
			result = parseQuery(queryUri, '&', '=', ",");
		}
		return result;
	}


	/**
	 * 枚举标识URI的部分,包含指示给定字符在特定URI组件中是否有效的方法
	 *
	 * @see <a href="http://www.ietf.org/rfc/rfc3986.txt">RFC 3986</a>
	 */
	enum URIPart {

		SCHEME {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isAlpha(c) || CharUtil.isDigit(c) || c == '+' || c == '-' || c == '.';
			}
		},
		//		AUTHORITY {
//			@Override
//			public boolean isValid(char c) {
//				return isUnreserved(c) || isSubDelimiter(c) || c == ':' || c == '@';
//			}
//		},
		USER_INFO {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isUnreserved(c) || CharUtil.isSubDelimiter(c) || c == ':';
			}
		},
		HOST {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isUnreserved(c) || CharUtil.isSubDelimiter(c);
			}
		},
		PORT {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isDigit(c);
			}
		},
		PATH {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isPchar(c) || c == '/';
			}
		},
		PATH_SEGMENT {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isPchar(c);
			}
		},
		QUERY {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isPchar(c) || c == '/' || c == '?';
			}
		},
		QUERY_PARAM {
			@Override
			public boolean isValid(char c) {
				if (c == '=' || c == '+' || c == '&' || c == ';') {
					return false;
				}
				return CharUtil.isPchar(c) || c == '/' || c == '?';
			}
		},
		FRAGMENT {
			@Override
			public boolean isValid(char c) {
				return CharUtil.isPchar(c) || c == '/' || c == '?';
			}
		};

		/**
		 * 指示此URI组件中是否允许给定字符.
		 * 如果允许字符true
		 */
		public abstract boolean isValid(char c);

	}

	public static class Builder {
		protected final StringBuilder url;
		protected final String        encoding;
		protected       boolean       hasParams;

		public Builder(String path, boolean encodePath, String encoding) {
			this.encoding = encoding;
			url = new StringBuilder();
			if (encodePath) {
				url.append(encode(path, encoding));
			} else {
				url.append(path);
			}
			this.hasParams = url.indexOf("?") != -1;
		}

		/**
		 * Appends new query parameter to the url.
		 */
		public Builder queryParam(String name, String value) {
			url.append(hasParams ? '&' : '?');
			hasParams = true;

			url.append(encodeQueryParam(name, encoding));

			if ((value != null) && (value.length() > 0)) {
				url.append('=');
				url.append(encodeQueryParam(value, encoding));
			}
			return this;
		}

		/**
		 * Returns full URL.
		 */
		@Override
		public String toString() {
			return url.toString();
		}
	}
}
